/* **************************************************************************
 * $OpenLDAP: pkg/jldap/com/novell/ldap/LDAPResponse.java,v 1.49 2006/08/17 08:33:34 npalani Exp $
 *
 * Copyright (C) 1999 - 2002 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 ******************************************************************************/

package com.novell.ldap;

import java.io.IOException;
import java.net.MalformedURLException;

import com.novell.ldap.asn1.ASN1Enumerated;
import com.novell.ldap.asn1.ASN1OctetString;
import com.novell.ldap.asn1.ASN1Sequence;
import com.novell.ldap.client.Debug;
import com.novell.ldap.client.ReferralInfo;
import com.novell.ldap.rfc2251.RfcAddResponse;
import com.novell.ldap.rfc2251.RfcCompareResponse;
import com.novell.ldap.rfc2251.RfcControls;
import com.novell.ldap.rfc2251.RfcDelResponse;
import com.novell.ldap.rfc2251.RfcIntermediateResponse;
import com.novell.ldap.rfc2251.RfcLDAPDN;
import com.novell.ldap.rfc2251.RfcLDAPMessage;
import com.novell.ldap.rfc2251.RfcLDAPString;
import com.novell.ldap.rfc2251.RfcModifyDNResponse;
import com.novell.ldap.rfc2251.RfcModifyResponse;
import com.novell.ldap.rfc2251.RfcReferral;
import com.novell.ldap.rfc2251.RfcResponse;
import com.novell.ldap.rfc2251.RfcSearchResultDone;

/**
 *  A message received from an LDAPServer
 *  in response to an asynchronous request.
 *
 *  <p>Sample Code: <a href="http://developer.novell.com/ndk/doc/samplecode/jldap_sample/asynchronous/Searchas.java.html">Searchas.java</p>
 *
 * @see LDAPConnection#search
 */

 /*
  * Note: Exceptions generated by the reader thread are returned
  * to the application as an exception in an LDAPResponse.  Thus
  * if <code>exception</code> has a value, it is not a server response,
  * but instad an exception returned to the application from the API.
  */
public class LDAPResponse extends LDAPMessage
{
    private InterThreadException exception = null;
    private ReferralInfo activeReferral;

	/**
	 * This constructor was added to support default Serialization
	 *
	 */
	public LDAPResponse()
	{
		super();
	}
    
    /**
     * Creates an LDAPResponse using an LDAPException.
     * Used to wake up the user following an abandon.
     * Note: The abandon doesn't have to be user initiated
     * but may be the result of error conditions.
     * <br>
     * Referral information is available if this connection created solely
     * to follow a referral.
     *
     *  @param ex  The exception
     * <br><br>
     *  @param activeReferral  The referral actually used to create the
     *                             connection
     */
    public LDAPResponse( InterThreadException ex,
                         ReferralInfo activeReferral)
    {
        exception = ex;
        this.activeReferral = activeReferral;
        if( Debug.LDAP_DEBUG) {
            Debug.trace( Debug.messages,
                "new LDAPResponse: referral " + (this.activeReferral != null) +
                "\n\texception:" +  ex.toString());
        }

        return;
    }

    /**
     * Creates a response LDAPMessage when receiving an asynchronous
     * response from a server.
     *
     *  @param message  The RfcLDAPMessage from a server.
     */
    /*package*/
    LDAPResponse( RfcLDAPMessage message)
    {
        super(message);
        return;
    }

    /**
     * Creates a SUCCESS response LDAPMessage. Typically the response
     * comes from a source other than a BER encoded LDAP message,
     * such as from DSML.  Other values which are allowed in a response
     * are set to their empty values.
     *
     * @param type  The message type as defined in LDAPMessage.
     *
     * @see LDAPMessage
     */
    public LDAPResponse( int type)
    {
        this(type, LDAPException.SUCCESS, null, null, null, null);
        return;
    }

    /**
     * Creates a response LDAPMessage from parameters. Typically the data
     * comes from a source other than a BER encoded LDAP message,
     * such as from DSML.
     *
     * @param type  The message type as defined in LDAPMessage.
     *
     * @param resultCode  The result code as defined in LDAPException.
     *
     * @param matchedDN   The name of the lowest entry that was matched
     *                    for some error result codes, an empty string
     *                    or <code>null</code> if none.
     *
     * @param serverMessage  A diagnostic message returned by the server,
     *                       an empty string or <code>null</code> if none.
     *
     * @param referrals   The referral URLs returned for a REFERRAL result
     *                    code or <code>null</code> if none.
     *
     * @param controls    Any controls returned by the server or
     *                    <code>null</code> if none.
     *
     * @see LDAPMessage
     * @see LDAPException
     */
    public LDAPResponse( int type,
                         int resultCode,
                         String matchedDN,
                         String serverMessage,
                         String[] referrals,
                         LDAPControl[] controls)
    {

        super( new RfcLDAPMessage(
                RfcResultFactory( type, resultCode, matchedDN,
                                  serverMessage, referrals)) );

        return;
    }

	/** Converts a LDAPControl array to an RfcControl Structure.
	* @param controls array of LDAPControl
	* @return RfcControls Structure representation of controls arrray.
	*/
	private static RfcControls RfcControlFactory(LDAPControl[] controls) {
		RfcControls rfcs = new RfcControls();

		if (controls != null) {

			for (int i = 0; i < controls.length; i++) {
				rfcs.add(controls[i].getASN1Object());
			}
			return rfcs;
		} else
			return null;

	}

	private static ASN1Sequence RfcResultFactory(   int type,
                                                    int resultCode,
                                                    String matchedDN,
                                                    String serverMessage,
                                                    String[] referrals)
    {
        ASN1Sequence ret;
		RfcReferral rfcreferal = null;
		if (referrals != null)
			{
				try{
					rfcreferal = new RfcReferral(referrals);
				}catch(MalformedURLException e)
				{
					//Referals are malformed
					e.printStackTrace();
				}
			} 
        if( matchedDN == null)
            matchedDN = "";
        if( serverMessage == null)
            serverMessage = "";
            
        switch( type) {
        case SEARCH_RESULT:
				ret =
					new RfcSearchResultDone(
						new ASN1Enumerated(resultCode),
                                           new RfcLDAPDN( matchedDN),
                                           new RfcLDAPString( serverMessage),
						rfcreferal);
            break;
        case BIND_RESPONSE:
            ret = null;                     // Not yet implemented
            break;                                   
        case SEARCH_RESPONSE:
				ret = null; // Not yet implemented, implemented as
				// separate classes.
            break;                                   
        case MODIFY_RESPONSE:
            ret = new RfcModifyResponse(   new ASN1Enumerated( resultCode),
                                           new RfcLDAPDN( matchedDN),
                                           new RfcLDAPString( serverMessage),
						rfcreferal);
            break;                                   
        case ADD_RESPONSE:
            ret = new RfcAddResponse(      new ASN1Enumerated( resultCode),
                                           new RfcLDAPDN( matchedDN),
                                           new RfcLDAPString( serverMessage),
						rfcreferal);
            break;                                   
        case DEL_RESPONSE:
            ret = new RfcDelResponse(      new ASN1Enumerated( resultCode),
                                           new RfcLDAPDN( matchedDN),
                                           new RfcLDAPString( serverMessage),
						rfcreferal);
            break;                                   
        case MODIFY_RDN_RESPONSE:
            ret = new RfcModifyDNResponse( new ASN1Enumerated( resultCode),
                                           new RfcLDAPDN( matchedDN),
                                           new RfcLDAPString( serverMessage),
						rfcreferal);
            break;                                   
        case COMPARE_RESPONSE:
            ret = new RfcCompareResponse(  new ASN1Enumerated( resultCode),
                                           new RfcLDAPDN( matchedDN),
                                           new RfcLDAPString( serverMessage),
						rfcreferal);
            break;                                   
        case SEARCH_RESULT_REFERENCE:
				ret = null; // Not yet implemented, implemented as 
				// LDAPSearchResultReference Object.
            break;                                   
        case EXTENDED_RESPONSE:
			ret = null; // Not yet implemented, implemented 
			//as LDAPExtendedResponse			
            break;                                   
        default:
            throw new RuntimeException("Type " + type + " Not Supported");
        }
        return ret;
    }

    /**
     * Returns any error message in the response.
     *
     * @return Any error message in the response.
     */
    public String getErrorMessage()
    {
        if( exception != null) {
            return exception.getLDAPErrorMessage();
        }
        return ((RfcResponse)message.getResponse()).getErrorMessage().stringValue();
    }

    /**
     * Returns the partially matched DN field from the server response,
     * if the response contains one.
     *
     * @return The partially matched DN field, if the response contains one.
     *
     */
    public String getMatchedDN()
    {
        if( exception != null) {
            return exception.getMatchedDN();
        }
        return ((RfcResponse)message.getResponse()).getMatchedDN().stringValue();
    }

    /**
     * Returns all referrals in a server response, if the response contains any.
     *
     * @return All the referrals in the server response.
     */
    public String[] getReferrals()
    {
        String[] referrals = null;
        RfcReferral ref = ((RfcResponse)message.getResponse()).getReferral();

        if(ref == null) {
            referrals = new String[0];
        } else {
            // convert RFC 2251 Referral to String[]
            int size = ref.size();
            referrals = new String[size];
            for(int i=0; i<size; i++) {
                String aRef = ((ASN1OctetString)ref.get(i)).stringValue();
                try {
                    // get the referral URL
                    LDAPUrl urlRef = new LDAPUrl( aRef);
                    if( urlRef.getDN() == null) {
                        RfcLDAPMessage origMsg =
                            super.getASN1Object().getRequestingMessage().getASN1Object();
                        String dn;
                        if( (dn = origMsg.getRequestDN()) != null) {
                            urlRef.setDN( dn);
                            aRef = urlRef.toString();
                        }
                    }
                } catch( MalformedURLException mex) {
                    ;
                } finally {
                    referrals[i] = aRef;
                }
            }
        }
        return referrals;
   }

    /**
     * Returns the result code in a server response.
     *
     * <p> For a list of result codes, see the LDAPException class. </p>
     *
     * @return The result code.
     */
    public int getResultCode()
    {
        if( exception != null) {
            return exception.getResultCode();
        }

		if (((RfcResponse)message.getResponse()) instanceof RfcIntermediateResponse)
			return 0;

        return ((RfcResponse)message.getResponse()).getResultCode().intValue();
    }

    /**
     * Checks the resultCode and throws the appropriate exception.
     *
     *  @exception LDAPException A general exception which includes an error
     *  message and an LDAP error code.
     */
    /* public */
    public void chkResultCode() throws LDAPException
    {
        if( exception != null) {
            throw exception;
        } else {
            if( Debug.LDAP_DEBUG) {
                Debug.trace( Debug.messages, "LDAPResponse: message(" +
                    getMessageID() + ") result code " + getResultCode());
            }
            LDAPException ex = getResultException();
            if( ex != null) {
                throw ex;
            }
            return;
        }
    }

    /**
     * Checks the resultCode and generates the appropriate exception or
     * null if success.
     */
    /* package */
    LDAPException getResultException()
    {
        LDAPException ex = null;
        switch(getResultCode()) {
        case LDAPException.SUCCESS:
        case LDAPException.COMPARE_TRUE:
        case LDAPException.COMPARE_FALSE:
            break;
        case LDAPException.REFERRAL:
            // only get here if automatic referral handling is not enabled.
            String[] refs = getReferrals();
            if( Debug.LDAP_DEBUG ) {
                Debug.trace( Debug.messages,
                            "LDAPResponse: Generating RfcReferral Exception");
                for( int i = 0; i < refs.length; i++) {
                    Debug.trace( Debug.messages, "LDAPResponse: \t" + refs[i]);
                }
            }
            ex = new LDAPReferralException(
                    "Automatic referral following not enabled",
                    LDAPException.REFERRAL, getErrorMessage());
            ((LDAPReferralException)ex).setReferrals( refs);
            break;
        default: // Everything else
            ex = new LDAPException(
                LDAPException.resultCodeToString( getResultCode()),
                getResultCode(), getErrorMessage(), getMatchedDN());
            break;
        }
        return ex;
    }

    /* Methods from LDAPMessage */

    /**
     * Returns any controls in the message.
     *
     * @see com.novell.ldap.LDAPMessage#getControls()
     */
    public LDAPControl[] getControls() {
        if( exception != null) {
            return null;
        }
        return super.getControls();
    }
    /**
     * Returns the message ID.
     *
     * @see com.novell.ldap.LDAPMessage#getMessageID()
     */
    public int getMessageID() {
        if( exception != null) {
            return exception.getMessageID();
       }
       return super.getMessageID();
    }

    /**
     * Returns the LDAP operation type of the message.
     *
     * @return The operation type of the message.
     *
     * @see com.novell.ldap.LDAPMessage#getType()
     */
    public int getType()
     {
        if( exception != null) {
           return exception.getReplyType();
        }
        return super.getType();
    }

    /**
     * Indicates if this response is an embedded exception response
     *
     * @return true if contains an embedded LDAPexception
     */
     /*package*/
     boolean hasException()
     {
        return (exception != null);
     }

    /**
     * Returns an embedded exception response
     *
     * @return an embedded exception if any
     */
     /*package*/
     LDAPException getException()
     {
        return exception;
     }

    /**
     * Indicates the referral instance being followed if the
     * connection created to follow referrals.
     *
     * @return the referral being followed
     */
     /*package*/
     ReferralInfo getActiveReferral()
     {
        return activeReferral;
     }
	protected void setDeserializedValues(LDAPMessage readObject, RfcControls asn1Ctrls)
	   throws IOException, ClassNotFoundException {
//	  Check if it is the correct message type
	  if(!(readObject instanceof LDAPResponse))
		throw new ClassNotFoundException("Error occured while deserializing " +
			"LDAPResponse object");

		LDAPResponse tmp = (LDAPResponse)readObject;
	
		int type = tmp.getType();
		int resultCode = tmp.getResultCode();
		String matchedDN = tmp.getMatchedDN();
		String serverMessage = tmp.getErrorMessage();
		String[] referrals = tmp.getReferrals();
		tmp = null; //remove reference after getting properties

		message = new RfcLDAPMessage(
			LDAPResponse.RfcResultFactory( type, resultCode, matchedDN,
				  serverMessage, referrals)); 	 
//		Garbage collect the readObject from readDSML()..	
		readObject = null;
   }       
}
